/**
  HomeMade by shoe[box]
 
  Redistribution and use in source and binary forms, with or without 
  modification, are permitted provided that the following conditions are
  met:

  * Redistributions of source code must retain the above copyright notice, 
    this list of conditions and the following disclaimer.
  
  * Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the 
    documentation and/or other materials provided with the distribution.
  
  * Neither the name of shoe[box] nor the names of its 
    contributors may be used to endorse or promote products derived from 
    this software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package org.shoebox.guacamole.view.containers {

	import org.shoebox.collections.Array2D;
	import org.shoebox.guacamole.materials.AnimateMaterial;
	import org.shoebox.guacamole.materials.abstracts.AMaterial;
	import org.shoebox.guacamole.materials.texturesAtlas.SubTextureAtlas;
	import org.shoebox.guacamole.view.abstracts.GuaMesh;
	import org.shoebox.utils.logger.Logger;
	import flash.display3D.Context3D;
	import flash.display3D.Context3DVertexBufferFormat;
	import flash.geom.Matrix3D;
	import flash.geom.Rectangle;
	
	/**
	* org.shoebox.guacamole.view.containers.GuaGrid
	* @author shoebox
	*/
	public class GuaGrid extends GuaMesh{
		
		public var tileSize			: int;
		public var gridWidth			: int;
		public var gridHeight			: int;
		
		protected var _bInvalidateGrps	: Boolean;
		protected var _indiceInc		: int;
		protected var _currentGroup		: RenderGroup;
		protected var _rViewport		: Rectangle;
		protected var _vGroups			: Vector.<RenderGroup>;
		protected var _vContent		: Vector.<Vector.<GuaMesh>>;
		
		internal var vContent 			: Vector.<GuaMesh> , id : int ;
		
		// -------o constructor
		
			/**
			* Constructor of the GuaGrid class
			*
			* @public
			* @return	void
			*/
			public function GuaGrid( gridWidth : int , gridHeight : int , tileSize : int ) : void {
				super( );
				
				this.tileSize 	= tileSize;
				this.gridWidth	= gridWidth;
				this.gridHeight	= gridHeight;
				_rViewport		= new Rectangle( );
				_bInvalidateGrps 	= true;
				_vContent 		= new Vector.<Vector.<GuaMesh>>( gridWidth * gridHeight , true );
				
				setSize( 1 , 1 );
				setScale( 1 , 1 );
			}
			
		// -------o public
			
			/**
			* getDatasAt function
			* @public
			* @param 
			* @return
			*/
			final public function getDatasAt( dx : int , dy : int ) : Vector.<GuaMesh> {
				return _vContent[ dy * gridWidth + dx];
			}
			
			/**
			* getDatasAt function
			* @public
			* @param 
			* @return
			*/
			final public function setDatasAt( dx : int , dy : int , datas : Vector.<GuaMesh> ) : void {
				_vContent[ dy * gridWidth + dx] = datas;
			}
			
			/**
			* addItemAt function
			* @public
			* @param 
			* @return
			*/
			final public function addItemAt( dx : int , dy : int , g : GuaMesh , bPos : Boolean = true ) : void {
				
				var vContent : Vector.<GuaMesh> = getDatasAt( dx , dy ) as Vector.<GuaMesh>;
				
				g.scene = scene;
				if( bPos ){
					g.x = tileSize * dx;
					g.y = tileSize * dy;
				}
				
				if( !vContent )
					vContent = new Vector.<GuaMesh>( );
					vContent.push( g );
						
				setDatasAt( dx , dy , vContent );
				_invalidate( );
			}
			
			/**
			* setViewport function
			* @public
			* @param 
			* @return
			*/
			final public function setViewport( dx : int , dy : int , dw : int , dh : int ) : void {
				
				var 	rec : Rectangle 	= new Rectangle( dx , dy , dw , dh );
				if( !rec.equals( _rViewport ) ){
					//x = dx * tileSize;
					//y = dy * tileSize;
					_rViewport = rec;
					_invalidate( );
				}
			}
			
			/**
			* render function
			* @public
			* @param 
			* @return
			*/
			final override public function render( oCam : Matrix3D ) : void {
				_cropGroups( );
				_renderGroups( oCam );
			}
			
			/**
			* removeItemAt function
			* @public
			* @param 
			* @return
			*/
			final public function removeItemAt( dx : int , dy : int , mesh : GuaMesh ) : Boolean {
				
				vContent = getDatasAt( dx , dy );
				if( !vContent )
					return false;				
				
				id = vContent.indexOf( mesh );
				if( id == -1 )
					return false;//throw new ArgumentError('The item '+mesh+' is not contained at the position '+dx+' - '+dy );
					
				vContent.splice( id , 1 );
				setDatasAt( dx, dy , vContent );
				return true;
			}
			
		// -------o protected
			
			/**
			* 
			*
			* @param 
			* @return
			*/
			final protected function _invalidate() : void {
				_bInvalidateGrps = true;
				_currentGroup = null;
				_vGroups = new Vector.<RenderGroup>( );
			}
			
			/**
			* 
			*
			* @param 
			* @return
			*/
			final protected function _cropGroups() : void {
				
				if( _bInvalidateGrps ){
					_bInvalidateGrps = false;
					_resetGroups( );
					
					var dx : int; 
					var dy : int = _rViewport.y;
					var dr : int = _rViewport.x + _rViewport.width + 1;
					var db : int = dy + _rViewport.height + 1;
					
					for( dy ; dy < db ; dy++ ){
						
						if( dy < 0 || dy >= gridHeight )
							continue;
						
						dx = _rViewport.x;
						for( dx ; dx < dr ; dx++ ){
							
							if( dx < 0 || dx >= gridWidth )
								continue;
							
							_merge( dx , dy );
						}
						
					}
					
				}
				
			}
			
			/**
			* 
			*
			* @param 
			* @return
			*/
			final protected function _merge( dx : int , dy : int ) : void {
				
				//
					vContent = getDatasAt( dx , dy );
					if( !vContent )
						return;
				
				//	
					var mesh : GuaMesh;
					var ind : Vector.<uint>;
					var i : int = 0 , l : int = vContent.length;
					var mat : AMaterial;
					for( i ; i < l ; i++ ){
						
						mesh = vContent[ i ];
						mat = ( mesh.material is SubTextureAtlas) ? ( mesh.material as SubTextureAtlas).atlas : mesh.material;
						if( !_currentGroup  || _currentGroup.material !== mat ){
							_newGroup( mat , mesh );
						}
						
						ind = mesh.getIndexs( true );
						ind = _incrementIndexs( ind , _indiceInc );
						
						//Uvs
							if( mesh.material is SubTextureAtlas ){
								_currentGroup.vUVCoords	= _currentGroup.vUVCoords.concat( ( mesh.material as SubTextureAtlas ).vUvs );
							}else if( mesh.material is AnimateMaterial ){
								//_currentGroup.vUVCoords = _currentGroup.vUVCoords.concat( ( mesh.material as AnimateMaterial ). );		
								_currentGroup.vUVCoords = _currentGroup.vUVCoords.concat( mesh.vUVCoords );
							}else{ 
								_currentGroup.vUVCoords = _currentGroup.vUVCoords.concat( mesh.vUVCoords );
							}
							
						// Indexes		
							_currentGroup.vIndexs 		= _currentGroup.vIndexs.concat( ind );
						
						// Vertices
							_currentGroup.vVerticesCoords = _currentGroup.vVerticesCoords.concat( mesh.getProjectVertices( ) );
						
						_indiceInc += ( mesh.vVerticesCoords.length / mesh.iBufferVerticesSize );
					}
					
				/*
					trace( _currentGroup.vIndexs );
					trace( _currentGroup.vUVCoords );
					trace( _currentGroup.vVerticesCoords );
				*/
			}
			
			
			/**
			* 
			*
			* @param 
			* @return
			*/
			final protected function _incrementIndexs( v : Vector.<uint> , inc : int ) : Vector.<uint> {
				
				var dz : int = 0;
				var len : int = v.length;
				for( dz ; dz < len ; dz++ ){
					v[ dz ] += inc;
				}
				
				return v;
				
			}
			
			/**
			* 
			*
			* @param 
			* @return
			*/
			final protected function _newGroup( mat : AMaterial , mesh : GuaMesh ) : void {
				
				_currentGroup = new RenderGroup( mat );
				if( _currentGroup.bDynamic )
					_currentGroup.refMesh = mesh;
					
				_vGroups.push( _currentGroup );
				_indiceInc = 0;
			}
			
			/**
			* 
			*
			* @param 
			* @return
			*/
			final protected function _resetGroups() : void {
				_vGroups = new Vector.<RenderGroup>( );
			}
			
			/**
			* 
			*
			* @param 
			* @return
			*/
			final protected function _renderGroups( oCam : Matrix3D ) : void {
				
				var l : int = _vGroups.length;
				var i : int = 0;
				var grp : RenderGroup;
				
				for( i ; i < l ; i++ ){
					
					
					grp = _vGroups[ i ];
					
					if( grp.bDynamic ){
						
						grp.refMesh.render( oCam );
						
					}else{
						
						material = grp.material;
						setIndexs( grp.vIndexs );
						setUvs( grp.vUVCoords , Context3DVertexBufferFormat.FLOAT_3 );
						setVertices( grp.vVerticesCoords 	, Context3DVertexBufferFormat.FLOAT_3 );
						super.render( oCam );
					}
						
					
				}
			}
			
		// -------o misc

			public static function trc(...args : *) : void {
				Logger.log(GuaGrid, args);
			}
	}
}


import org.shoebox.guacamole.materials.AnimateMaterial;
import org.shoebox.guacamole.view.abstracts.GuaMesh;
import org.shoebox.guacamole.materials.abstracts.AMaterial;
internal class RenderGroup{
	
	public var material			: AMaterial;
	public var vUVCoords 			: Vector.<Number>;
	public var vVerticesCoords 		: Vector.<Number>;
	public var vIndexs 			: Vector.<uint>;
	public var refMesh			: GuaMesh;
	public var bDynamic			: Boolean = false;
	
	/**
	* RenderGroup function
	* @public
	* @param 
	* @return
	*/
	final public function RenderGroup( material : AMaterial ) : void {
		this.material 	= material;
		this.bDynamic 	= material is AnimateMaterial;
		vUVCoords 		= new Vector.<Number>( );
		vVerticesCoords 	= new Vector.<Number>( );
		vIndexs 		= new Vector.<uint>( );
	}
	
}